/*******************************************************************************
 * Copyright (c) 2017-2020, org.smartboot. All rights reserved.
 * project name: smart-http
 * file name: HttpOutputStream.java
 * Date: 2020-01-01
 * Author: sandao (zhengjunweimail@163.com)
 ******************************************************************************/

package org.smartboot.http.server;

import java.io.IOException;
import java.time.Clock;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.smartboot.http.enums.HeaderNameEnum;
import org.smartboot.http.enums.HttpStatus;
import org.smartboot.http.utils.HttpHeaderConstant;
import org.smartboot.socket.transport.WriteBuffer;

import com.google.common.collect.Maps;

/**
 * @author 三刀
 * @version V1.0 , 2018/2/3
 */
public class HttpOutputStream extends AbstractOutputStream {
	/**
	 * key:status+contentType
	 */
	private static final Map<String, byte[]>[] CACHE_CONTENT_TYPE_AND_LENGTH = new Map[512];
	/**
	 * Key：status+contentType
	 */
	private static final Map<String, byte[]> CACHE_CHUNKED_AND_LENGTH = new HashMap<>();

	static {
		for (int i = 0; i < CACHE_CONTENT_TYPE_AND_LENGTH.length; i++) {
			CACHE_CONTENT_TYPE_AND_LENGTH[i] = new HashMap<>();
		}
	}

	public HttpOutputStream(HttpRequestImpl request, HttpResponseImpl response, WriteBuffer writeBuffer) {
		super(request, response, writeBuffer);
	}

	/**
	 * 输出Http消息头
	 *
	 * @throws IOException
	 */
	final protected synchronized void writeHead() throws IOException {
		if (committed) {
			return;
		}
		if (response.getHttpStatus() == null) {
			response.setHttpStatus(HttpStatus.OK);
		}
		String contentType = response.getContentType();

		if (contentType == null) {
			contentType = HttpHeaderConstant.Values.DEFAULT_CONTENT_TYPE;
		}

		//输出http状态行、contentType,contentLength、Transfer-Encoding、server等信息

		byte[] headPart = getHeadPart(contentType);
		System.out.println(new String(headPart));
		writeBuffer.write(headPart);

		//转换Cookie
		convertCookieToHeader(response);

		//输出Header部分

		Map<String, HeaderValue> headerMaps = response.getHeaders();

		if (headerMaps == null)
			headerMaps = Maps.newConcurrentMap();

		if (chunked) {
			headerMaps.remove(HttpHeaderConstant.Names.TRANSFER_ENCODING);
		}

		if (headerMaps.get(HeaderNameEnum.DATE.getName()) == null) {
			headerMaps.put(HttpHeaderConstant.Names.DATE, new HeaderValue(HttpHeaderConstant.Names.DATE, getDate()));
		}

		if (headerMaps.get(HeaderNameEnum.SERVER.getName()) == null) {
			headerMaps.put(HttpHeaderConstant.Names.SERVER, new HeaderValue(HttpHeaderConstant.Names.SERVER, getServer()));
		}

		//byte[] headerNameValueList = null;

		for (Map.Entry<String, HeaderValue> entry : headerMaps.entrySet()) {
			HeaderValue headerValue = entry.getValue();
			while (headerValue != null) {
				byte[] headerNameValue = getHeaderNameBytes(entry.getKey());
				byte[] headerValueb = getBytes(headerValue.getValue());
				writeBuffer.write(headerNameValue);
				writeBuffer.write(headerValueb);
				//	headerNameValueList = ArrayUtils.addAll(headerNameValueList, headerNameValue);
				headerValue = headerValue.getNextValue();
			}
		}

		//System.out.println(new String(ArrayUtils.addAll(headPart, headerNameValueList)));

		/**
		 * RFC2616 3.3.1
		 * 只能用 RFC 1123 里定义的日期格式来填充头域 (header field)的值里用到 HTTP-date 的地方
		 */

		//flushDate();
		//writeBuffer.write(DATE_SERVER_BYTES);

		writeBuffer.write("\r\n\r\n".getBytes());
		committed = true;
	}

	private synchronized byte[] getHeadPart(String contentType) {
		HttpStatus httpStatus = response.getHttpStatus();
		int contentLength = response.getContentLength();
		chunked = contentLength < 0;
		byte[] data = null;
		//成功消息优先从缓存中加载
		boolean cache = httpStatus == HttpStatus.OK;
		boolean http10 = Request.HTTP_1_0_STRING.equals(request.getProtocol());
		if (cache && !http10) {
			if (chunked) {
				data = CACHE_CHUNKED_AND_LENGTH.get(contentType);
			} else if (contentLength < CACHE_CONTENT_TYPE_AND_LENGTH.length) {
				data = CACHE_CONTENT_TYPE_AND_LENGTH[contentLength].get(contentType);
			}
			if (data != null) {
				return data;
			}
		}

		String str = request.getProtocol() + httpStatus.getHttpStatusLine() + "\r\n" + HttpHeaderConstant.Names.CONTENT_TYPE + ":" + contentType;
		if (contentLength >= 0) {
			str += "\r\n" + HttpHeaderConstant.Names.CONTENT_LENGTH + ":" + contentLength;
		} else if (chunked) {
			str += "\r\n" + HttpHeaderConstant.Names.TRANSFER_ENCODING + ":" + HttpHeaderConstant.Values.CHUNKED;
		}
		data = str.getBytes();
		//缓存响应头
		if (cache && !http10) {
			if (chunked) {
				CACHE_CHUNKED_AND_LENGTH.put(contentType, data);
			} else if (contentLength >= 0 && contentLength < CACHE_CONTENT_TYPE_AND_LENGTH.length) {
				CACHE_CONTENT_TYPE_AND_LENGTH[contentLength].put(contentType, data);
			}
		}
		// http 1.0 不支持 chunked
		if (chunked && http10) {
			chunked = false;
		}
		return data;
	}

	private void convertCookieToHeader(AbstractResponse response) {
		List<Cookie> cookies = response.getCookies();
		if (cookies == null || cookies.size() == 0) {
			return;
		}
		cookies.forEach(cookie -> {
			response.addHeader(HttpHeaderConstant.Names.SET_COOKIE, cookie.toString());
		});

	}

	public static void main(String[] args) {
		ZonedDateTime datetime = ZonedDateTime.now(Clock.system(ZoneId.of("GMT")));
		DateTimeFormatter formatter = DateTimeFormatter.ofPattern("E, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH);
		System.out.println(datetime.format(formatter));

	}

}
